package com.zmn.mcc.permit.mysql.parsing;

import com.google.common.base.Joiner;
import com.zmn.mcc.permit.mysql.annotation.ZmnDPermit;
import com.zmn.mcc.permit.mysql.annotation.model.ZmnDPermitConfigModel;
import com.zmn.mcc.permit.mysql.annotation.model.ZmnDPermitModel;
import com.zmn.mcc.permit.mysql.cache.DPermitCache;
import com.zmn.mcc.permit.mysql.exception.ZmnDPermitException;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * SQL解析器
 *
 * @author duanzuocai
 * @version v1.0
 * @since 2020/6/18 14:48
 */
@Service
public class ZmnDPermitSqlParsing {

    private final Logger logger = LoggerFactory.getLogger(ZmnDPermitSqlParsing.class);

    private static final String TAG = "[zmn]";

    private static String EQ = " = ";
    private static String IN = " in ";

    @Resource
    DPermitCache dPermitCache;

    private CCJSqlParserManager parserManager = new CCJSqlParserManager();


    /**
     * 计算新SQL
     * 根据DAO注解他调用链，获取上层调用是否有Top注解，如果有，根据两个注解的权限类型匹配读取数据库权限配置，修改SQL
     *
     * @param oldSql             老SQL
     * @param daoAnnotationModel DAO上的注解
     * @param staffObj           进行数据权限筛选的员工
     * @return
     * @throws ClassNotFoundException
     */
    public String getNewSql(String oldSql, ZmnDPermitConfigModel daoAnnotationModel, Object staffObj) throws ClassNotFoundException {

        String newSql = null;

        StackTraceElement[] methodLink = Thread.currentThread().getStackTrace();

        // 跟踪调用链，获取TOP注解
        for (int i = 0; i < methodLink.length; i++) {
            StackTraceElement trace = Thread.currentThread().getStackTrace()[i];
            String clazz = trace.getClassName();

            if (!clazz.startsWith("com.zmn") || clazz.startsWith("com.zmn.mcc.interceptor")) {
                continue;
            }

            // dubbo, service 添加注解的方法不能有重载，会区分不出使用哪个
            String method = trace.getMethodName();

            // 记录重名方法数量
            int topMethodNum = 0;

            // 循环上层调用类的方法
            ZmnDPermit topAnnotation = null;
            ZmnDPermitModel zmnDPermitModel = null;

            Method[] topMethods = Class.forName(clazz).getMethods();
            for (Method m : topMethods) {
                if (!m.getName().equals(method)) {
                    continue;
                }

                topMethodNum++;

                // 从下往上找调用链，如果找到就结束循环
                topAnnotation = m.getAnnotation(ZmnDPermit.class);
                zmnDPermitModel = new ZmnDPermitModel(clazz, m.getName(), topAnnotation);
            }

            // 校验：重名方法
            if (topMethodNum > 1 && topAnnotation != null) {
                logger.debug("{} {}.{}方法重名，数据权限注解失效", TAG, clazz, method);
                return null;
            }

            // 上级调用没有开启注解，继续往上找
            if (topAnnotation == null) {
                continue;
            }

            // 别名
            String alias = daoAnnotationModel.getAnnotation().alias();

            Map<String, String> argSubqueries = new HashMap<>();
            // 获取数据权限：字段 - IDS
            Map<String, List<Integer>> args;
            try {
                args = dPermitCache.getUserDPermitCache(daoAnnotationModel, zmnDPermitModel, staffObj, argSubqueries);
            } catch (ZmnDPermitException e) {
                logger.error(e.getMessage());
                return oldSql;
            }

            // 开始处理SQL人Where条件
            try {
                newSql = modifySqlWhere(oldSql, alias, args, argSubqueries);
            } catch (JSQLParserException e) {
                logger.error("原始SQL解析异常，请检查原始SQL语句", e);
                return null;
            }

            logger.debug("{} SQL拦截 Interceptor, newSql: {}", TAG, newSql);

            break;
        }

        return newSql;
    }

    /**
     * 根据数据权限更新SQL
     *
     * @param oldSql 老SQL
     * @param alias  主表别名
     * @param args   数据权限：key, value's
     * @return
     * @throws JSQLParserException
     */
    private String modifySqlWhere(String oldSql, String alias, Map<String, List<Integer>> args, Map<String, String> argSubqueries) throws JSQLParserException {
        if (args == null || args.size() == 0) {
            return oldSql;
        }

        CCJSqlParserManager parserManager = new CCJSqlParserManager();

        Select select = (Select) parserManager.parse(new StringReader(oldSql));

        PlainSelect plain = (PlainSelect) select.getSelectBody();
        Expression where_expression = plain.getWhere();

        // 添加条件
        StringBuilder sb = new StringBuilder();

        // 是否有初始条件，如果没有，第一个and不用添加
        boolean noWhere = where_expression == null
                || StringUtils.isEmpty(where_expression.toString());

        if (!noWhere) {
            sb.append(where_expression.toString());
        }

        for (String column : args.keySet()) {
            List<Integer> ids = args.get(column);

            if (ids.size() == 0) {
                continue;
            }

            String condition = ids.size() > 1 ? IN : EQ;

            String value = Joiner.on(",").join(ids);

            // 有子查询
            String subqueries = argSubqueries.get(column);
            if (StringUtils.isNotBlank(subqueries)) {
                condition = IN;
                value = subqueries.replaceAll("#" + column + "#", value);
            }

            if (noWhere) {
                noWhere = false;
            } else {
                sb.append(" AND ");
            }

            if (condition.equals(EQ)) {
                sb.append(alias).append(".").append(column).append(condition).append(value);
            } else {
                sb.append(alias).append(".").append(column).append(condition).append("(").append(value).append(")");
            }
        }


        Expression where = CCJSqlParserUtil.parseCondExpression(sb.toString());
        plain.setWhere(where);

        return select.toString();
    }

}
